package edu.tum.cs.stageflow.executionframework;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;

public class StageRunner extends Thread {
	private static final String DIR_BENCHMARK_RESULTS_WIN = "C:/StageFlowTestDir/benchmarkresults/";
	private static final String DIR_BENCHMARK_RESULTS = "/tmp/benchmarkresults/";
	private static final String DIR_LIVE_SCHEDULE_WIN = "C:/StageFlowTestDir/liveSchedule/";
	private static final String DIR_LIVE_SCHEDULE = "/tmp/liveSchedule/";

	private static final boolean IS_LIVE_TRACKING = false;
	private static final boolean DEBUG_OUTPUT = false;
	private static final int LIVE_TRACKING_FREQU = 10;

	private final IStage m_stage;
	private long executionCounter = 0;

	private final LinkedList<TimingResult> timingResults;
	private PrintWriter liveTrackingWriter;
	private int liveTrackingCounter = 0;

	private class TimingResult {
		private String stageName = "";
		private long startTime = -1;
		private long startExecutionTime = -1;
		private long stopExecutionTime = -1;
		private long stopTime = -1;

		public TimingResult(String stageName, long startTime) {
			this.stageName = stageName;
			this.startTime = startTime;
		}

		public TimingResult() {
		}

		long getStartTime() {
			return startTime;
		}

		void setStartTime(long startTime) {
			this.startTime = startTime;
		}

		long getStopTime() {
			return stopTime;
		}

		void setStopTime(long stopTime) {
			this.stopTime = stopTime;
		}

		private long getStartExecutionTime() {
			return startExecutionTime;
		}

		private void setStartExecutionTime(long startExecutionTime) {
			this.startExecutionTime = startExecutionTime;
		}

		private long getStopExecutionTime() {
			return stopExecutionTime;
		}

		private void setStopExecutionTime(long stopExecutionTime) {
			this.stopExecutionTime = stopExecutionTime;
		}

		public String getStageName() {
			return stageName;
		}

		public void setStageName(String stageName) {
			this.stageName = stageName;
		}

	}

	public StageRunner(IStage stage) {
		this.m_stage = stage;
		this.timingResults = new LinkedList<TimingResult>();
		if (IS_LIVE_TRACKING) {
			try {
				liveTrackingWriter = new PrintWriter(DIR_LIVE_SCHEDULE
						+ m_stage.toString() + ".sf", "UTF-8");
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	@Override
	public void run() {
		while (!m_stage.isFinished()) {
			TimingResult timer = new TimingResult(m_stage.getClass()
					.getSimpleName(), System.nanoTime());
			if (DEBUG_OUTPUT) {
				System.out.println(m_stage.toString() + " Reading Inputs...");
				System.out.flush();
			}
			m_stage.readInputs();
			timer.setStartExecutionTime(System.nanoTime());
			if (m_stage.isFinished()) {
				break;
			}
			if (DEBUG_OUTPUT) {
				System.out.println(m_stage.toString()
						+ " Starting Execution...");
				System.out.flush();
			}
			m_stage.execute();
			timer.setStopExecutionTime(System.nanoTime());
			if (DEBUG_OUTPUT) {
				System.out.println(m_stage.toString() + " Writing Outputs...");
				System.out.flush();
			}
			m_stage.writeOutputs();
			timer.setStopTime(System.nanoTime());
			timingResults.add(timer);
			executionCounter++;
			if (DEBUG_OUTPUT) {
				System.out.println(m_stage.toString() + " Done...");
				System.out.flush();
			}
			if (IS_LIVE_TRACKING && executionCounter % LIVE_TRACKING_FREQU == 1) {
				exportLiveTracker();
			}
		}
		m_stage.terminateNetwork();
		System.out.println(m_stage.toString() + " is finshed.");
		if (IS_LIVE_TRACKING) {
			exportLiveTracker();
			liveTrackingWriter.close();
		}
		exportBenchmarkResults();
	}

	private void exportLiveTracker() {
		for (int i = liveTrackingCounter; i < timingResults.size(); i++) {
			TimingResult result = timingResults.get(i);
			liveTrackingWriter.println(result.stageName + "(" + i + ")("
					+ result.startTime + "L,"
					+ (result.startExecutionTime - result.startTime) + "L,"
					+ (result.stopExecutionTime - result.startExecutionTime)
					+ "L," + (result.stopTime - result.stopExecutionTime)
					+ "L," + result.stopTime + "L)");
		}
		liveTrackingCounter = timingResults.size() - 1;
	}

	/*
	 * public static void main(String[] args) { SobelFilter filter = new
	 * SobelFilter(); filter.getRunner().start(); }
	 * 
	 * public static void main(String[] args) {
	 * java.util.concurrent.ArrayBlockingQueue myQueue = new
	 * java.util.concurrent.ArrayBlockingQueue<java.lang.String>(1024);
	 * SobelFilter filter = new SobelFilter(myQueue);
	 * filter.getRunner().start(); try { for (int i = 0; i < 64; i++) {
	 * myQueue.put(new String("WHAAAT")); } } catch (InterruptedException e) {
	 * // TODO Auto-generated catch block e.printStackTrace(); } }
	 */

	private void exportBenchmarkResults() {
		System.out
				.println("StageRunner: Starting to compute benchmark results...");
		int num_results = timingResults.size();

		// Start File Writer
		String outputPath = "";
		if (System.getProperty("os.name").startsWith("Windows")) {
			outputPath = DIR_BENCHMARK_RESULTS_WIN;
		} else {
			outputPath = DIR_BENCHMARK_RESULTS;
		}
		File dir = new File(outputPath);
		dir.mkdirs();
		PrintWriter writer = null;
		try {
			writer = new PrintWriter(outputPath + m_stage.toString() + ".csv",
					"UTF-8");
		} catch (FileNotFoundException | UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		double totalTime = 0.0;
		double totalReadTime = 0.0;
		double totalExecutionTime = 0.0;
		double totalWriteTime = 0.0;

		

		/* compute durations */
		for (TimingResult tResult : timingResults) {
			long time = (tResult.getStopTime() - tResult.getStartTime());
			totalTime += time / 1000.0;

			time = (tResult.getStartExecutionTime() - tResult.getStartTime());
			totalReadTime += time / 1000.0;

			time = (tResult.getStopExecutionTime() - tResult
					.getStartExecutionTime());
			totalExecutionTime += time / 1000.0;

			time = (tResult.getStopTime() - tResult.getStopExecutionTime());
			totalWriteTime += time / 1000.0;
		}

		System.out.println("StageRunner: Computing times done.");
		
		/* Computing average total time */
		double averageTotalTime = totalTime / num_results;
		double averageTotalReadTime = totalReadTime / num_results;
		double averageTotalExecutionTime = totalExecutionTime / num_results;
		double averageTotalWriteTime = totalWriteTime / num_results;

		/* Writing Title */
		String result = "Benchmark Results for ;;;;;;;;;;;;"
				+ m_stage.toString() + "\n";
		
		writeOutput(writer, result);
		result = "";

		/* Concluding Values */
		writeConcludingValue(num_results, writer, averageTotalTime,
				averageTotalReadTime, averageTotalExecutionTime,
				averageTotalWriteTime, totalTime);
		
		
		/* Single Results */
		writeSingleValues(writer, averageTotalTime, averageTotalReadTime,
				averageTotalExecutionTime, averageTotalWriteTime);


		writer.close();
	}

	private void writeSingleValues(PrintWriter writer, double averageTotalTime,
			double averageTotalReadTime, double averageTotalExecutionTime,
			double averageTotalWriteTime) {
		String result = "TotalDuration (micros);(ms);(dev);ReadTime (micros);(ms);(dev);ExecutionTime (micros);(ms);(dev);WriteTime (micros);(ms);(dev);\n";
		writeOutput(writer, result);
		
		for (TimingResult tResult : timingResults) {
			/* total time */
			String singleResults = "";
			double time = ((tResult.getStopTime() - tResult.getStartTime()) / 1000.0);
			singleResults += time + ";";
			singleResults += (time / 1000.0) + ";";
			singleResults += (time - averageTotalTime) + ";";

			/* read time */
			time = ((tResult.getStartExecutionTime() - tResult.getStartTime()) / 1000.0);
			singleResults += (time) + ";";
			singleResults += (time / 1000.0) + ";";
			singleResults += (time - averageTotalReadTime) + ";";

			/* execution time */
			time = ((tResult.getStopExecutionTime() - tResult
					.getStartExecutionTime()) / 1000.0);
			singleResults += (time) + ";";
			singleResults += (time / 1000.0) + ";";
			singleResults += (time - averageTotalExecutionTime) + ";";

			/* write time */
			time = ((tResult.getStopTime() - tResult.getStopExecutionTime()) / 1000.0);
			singleResults += (time) + ";";
			singleResults += (time / 1000.0) + ";";
			singleResults += (time - averageTotalWriteTime) + ";";

			singleResults += "\n";
			//write to output
			writeOutput(writer, singleResults);
		}
	}

	private void writeConcludingValue(int num_results, PrintWriter writer,
			double averageTotalTime, double averageTotalReadTime,
			double averageTotalExecutionTime, double averageTotalWriteTime, double totalTime) {
		
		double totalTimeDev = 0.0;
		double totalReadTimeDev = 0.0;
		double totalExecutionTimeDev = 0.0;
		double totalWriteTimeDev = 0.0;
		
		for (TimingResult tResult : timingResults) {
			double time = ((tResult.getStopTime() - tResult.getStartTime()) / 1000.0);
			totalTimeDev += java.lang.Math.pow(time - averageTotalTime, 2);			
			
			time = ((tResult.getStartExecutionTime() - tResult.getStartTime()) / 1000.0);
			totalReadTimeDev += java.lang.Math.pow(time - averageTotalReadTime,
					2);
			
			time = ((tResult.getStopExecutionTime() - tResult
					.getStartExecutionTime()) / 1000.0);
			totalExecutionTimeDev += java.lang.Math.pow(time
					- averageTotalExecutionTime, 2);
			
			time = ((tResult.getStopTime() - tResult.getStopExecutionTime()) / 1000.0);
			totalWriteTimeDev += java.lang.Math.pow(time
					- averageTotalWriteTime, 2);
		}
		String result = "";
		result += "Average total duration(micros);(ms);(dev);Average ReadTime(micros);(ms);(dev);Average ExecutionTime (micros);(ms);(dev);Average WriteTime (micros);(ms);(dev);\n";
		/* total time */
		result += (averageTotalTime) + ";";
		result += (averageTotalTime / 1000.0) + ";";
		result += (java.lang.Math.sqrt(totalTimeDev / num_results)) / 1000
				+ ";";

		/* read time */
		result += (averageTotalReadTime) + ";";
		result += (averageTotalReadTime / 1000.0) + ";";
		result += (java.lang.Math.sqrt(totalReadTimeDev / num_results)) / 1000
				+ ";";

		/* execution time */
		result += (averageTotalExecutionTime) + ";";
		result += (averageTotalExecutionTime / 1000.0) + ";";
		result += (java.lang.Math.sqrt(totalExecutionTimeDev / num_results))
				/ 1000 + ";";

		/* write time */
		result += averageTotalWriteTime + ";";
		result += averageTotalWriteTime / 1000.0 + ";";
		result += (java.lang.Math.sqrt(totalWriteTimeDev / num_results)) / 1000
				+ ";";

		result += "\n";
		result += "Number of Elements;Total Seconds;Elemente/Sec;Sec/Element\n";
		result += num_results + ";" + (totalTime / 1000000.0) + ";" + num_results/(totalTime / 1000000.0) + ";"+(totalTime / 1000000.0)/num_results;
		result += "\n\n";
		writeOutput(writer, result);
	}

	private void writeOutput(PrintWriter writer, String result) {
		String german_result = result.replace(".", ",");

		writer.print(german_result);
	}
}
